Module 4: Collecting data from APIs and PDFs, automated production in MS Office from R, and OpenAI ChatGPT from R

Week 07: R data ecosystem: packages for accessing various data sources. Understanding JSON and XML for API calls. Parse PDF files.

What do we want to do today?

In this session, we delve into the practicalities of importing and processing data from various sources using R, a crucial skill for any data analyst. Our exploration starts with making simple API calls to fetch data, providing a foundation for understanding the structure and format of JSON and XML data. We will then advance to more complex API interactions and data processing techniques, equipping you with the skills to handle real-world data extraction tasks efficiently. Additionally, we’ll cover methods for extracting data from PDF files, an essential capability for dealing with unstructured data. The session ends with a comprehensive case study utilizing World Bank data, where we’ll apply our learned techniques to analyze and report on global economic indicators.

Feedback should be send to goran.milovanovic@datakolektiv.com. These notebooks accompany the ADVANCED ANALYST - Foundations for Advanced Data Analytics in R DataKolektiv training.


Welcome to R!

1. Understanding, making, and parsing API calls

Until now, we have used only data stored as .csv or .xlsx files. As we have seen it was possible to map such files 1:1 onto dataframes in R: columns were defined, with the first rows holding column names by convention… However, in Data Science, many times we face the situation in which we have to obtain some data from a source that does not necessarily deliver “tabular” data structures. Also, many times we will be facing data structures that are essentially not “tabular”: for example, data structures in which some elements contain certain fields which other elements do not - and not because there are missing data, but because sometimes it does not make sense for something to be described by a certain attribute at all. Imagine describing David Bowie, a famous English musician, by a data structure: we can know his date of birth, and the release dates of his albums perhaps, but should be place all that data into one single column? No, because the semantics of such a field would be weird, of course. How would we organize the rows in a dataframe describing David Bowie: the first row describes the person, while other rows describe his works of art? So, we have a column for dateOfBirth, and that column has a value in the first row of the dataframe only, and then we have a column for releaseDate, and that column holds NA in the first row and then a timestamp in all other rows that refer to his albums? Wait, what about his spouse, children, collaborators: assign a row in a dataframe to each one?

No. Of course, a list in R would do, correct?

In the following example we will access a free REST API from within our R environment, collect the API response as JSON, convert it to an R list, and play with the data.

Setup the basic API access parameters

In this example we will rely on the free https://datausa.io/ API to obtain statistical data. Here is the intro to their API: datausa.io API.

  • You will find the API base endpoint there:
baseEndPoint <- "https://datausa.io/api/data"

Make a simple API call

We will use {httr} to get in touch with the API. It is a part of {tidyverse}.

library(httr)

Step 1. Define API parameters.

First we define the API parameters.

### --- compose API call
# - use base API endpoint
# - and concatenate with API parameters
# - from the following example: https://datausa.io/about/api/
# - parameter: drilldowns
drilldowns <- paste0("drilldowns=", "Nation")
# - parameter: measures
measures <- paste0("measures=", "Population")
# - parameters:
params <- paste("&", c(drilldowns, measures),
                sep = "", collapse = "")
cat(params)
&drilldowns=Nation&measures=Population

Step 2. Compose API call.

We put together the baseEndPoint with the API call parameters:

api_call <- paste0(baseEndPoint, "?", params)
cat(api_call)
https://datausa.io/api/data?&drilldowns=Nation&measures=Population

Step 3. Make API call.

We use httr::GET() to contact the API, ask for data, and fetch the result:

response <- httr::GET(URLencode(api_call))
class(response)
[1] "response"

The URLencode(api_call) call to the base R URLencode() function will take care of Percent-encoding where and if necessary. Hint: always use URLencode(your_api_call).

We can see that response is now of a response class. It is pretty structured and rich indeed:

str(response)
List of 10
 $ url        : chr "https://datausa.io/api/data?&drilldowns=Nation&measures=Population"
 $ status_code: int 200
 $ headers    :List of 25
  ..$ date                            : chr "Sun, 02 Jun 2024 12:39:56 GMT"
  ..$ content-type                    : chr "application/json; charset=utf-8"
  ..$ x-dns-prefetch-control          : chr "off"
  ..$ strict-transport-security       : chr "max-age=15552000; includeSubDomains"
  ..$ x-download-options              : chr "noopen"
  ..$ x-content-type-options          : chr "nosniff"
  ..$ x-xss-protection                : chr "1; mode=block"
  ..$ content-language                : chr "en"
  ..$ etag                            : chr "W/\"6e4-0ge41tM7RM4ctHp0uTGcx2UYtjI\""
  ..$ vary                            : chr "Accept-Encoding"
  ..$ content-encoding                : chr "gzip"
  ..$ last-modified                   : chr "Sun, 02 Jun 2024 12:39:56 GMT"
  ..$ x-cache-status                  : chr "MISS"
  ..$ x-frame-options                 : chr "SAMEORIGIN"
  ..$ access-control-allow-origin     : chr "*"
  ..$ access-control-allow-credentials: chr "true"
  ..$ access-control-allow-methods    : chr "GET, POST, OPTIONS"
  ..$ access-control-allow-headers    : chr "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type"
  ..$ x-cache-key                     : chr "https://datausa.io/api/data?&drilldowns=Nation&measures=Population"
  ..$ cache-control                   : chr "max-age=1800"
  ..$ cf-cache-status                 : chr "MISS"
  ..$ report-to                       : chr "{\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=XXdW%2FlkShqb3VIe3dbjE42qpIB13a1QiW"| __truncated__
  ..$ nel                             : chr "{\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}"
  ..$ server                          : chr "cloudflare"
  ..$ cf-ray                          : chr "88d7824e4d235b9b-VIE"
  ..- attr(*, "class")= chr [1:2] "insensitive" "list"
 $ all_headers:List of 1
  ..$ :List of 3
  .. ..$ status : int 200
  .. ..$ version: chr "HTTP/2"
  .. ..$ headers:List of 25
  .. .. ..$ date                            : chr "Sun, 02 Jun 2024 12:39:56 GMT"
  .. .. ..$ content-type                    : chr "application/json; charset=utf-8"
  .. .. ..$ x-dns-prefetch-control          : chr "off"
  .. .. ..$ strict-transport-security       : chr "max-age=15552000; includeSubDomains"
  .. .. ..$ x-download-options              : chr "noopen"
  .. .. ..$ x-content-type-options          : chr "nosniff"
  .. .. ..$ x-xss-protection                : chr "1; mode=block"
  .. .. ..$ content-language                : chr "en"
  .. .. ..$ etag                            : chr "W/\"6e4-0ge41tM7RM4ctHp0uTGcx2UYtjI\""
  .. .. ..$ vary                            : chr "Accept-Encoding"
  .. .. ..$ content-encoding                : chr "gzip"
  .. .. ..$ last-modified                   : chr "Sun, 02 Jun 2024 12:39:56 GMT"
  .. .. ..$ x-cache-status                  : chr "MISS"
  .. .. ..$ x-frame-options                 : chr "SAMEORIGIN"
  .. .. ..$ access-control-allow-origin     : chr "*"
  .. .. ..$ access-control-allow-credentials: chr "true"
  .. .. ..$ access-control-allow-methods    : chr "GET, POST, OPTIONS"
  .. .. ..$ access-control-allow-headers    : chr "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type"
  .. .. ..$ x-cache-key                     : chr "https://datausa.io/api/data?&drilldowns=Nation&measures=Population"
  .. .. ..$ cache-control                   : chr "max-age=1800"
  .. .. ..$ cf-cache-status                 : chr "MISS"
  .. .. ..$ report-to                       : chr "{\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=XXdW%2FlkShqb3VIe3dbjE42qpIB13a1QiW"| __truncated__
  .. .. ..$ nel                             : chr "{\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}"
  .. .. ..$ server                          : chr "cloudflare"
  .. .. ..$ cf-ray                          : chr "88d7824e4d235b9b-VIE"
  .. .. ..- attr(*, "class")= chr [1:2] "insensitive" "list"
 $ cookies    :'data.frame':    0 obs. of  7 variables:
  ..$ domain    : logi(0) 
  ..$ flag      : logi(0) 
  ..$ path      : logi(0) 
  ..$ secure    : logi(0) 
  ..$ expiration: 'POSIXct' num(0) 
  ..$ name      : logi(0) 
  ..$ value     : logi(0) 
 $ content    : raw [1:1764] 7b 22 64 61 ...
 $ date       : POSIXct[1:1], format: "2024-06-02 12:39:56"
 $ times      : Named num [1:6] 0 0.0578 0.0806 0.1311 0.7612 ...
  ..- attr(*, "names")= chr [1:6] "redirect" "namelookup" "connect" "pretransfer" ...
 $ request    :List of 7
  ..$ method    : chr "GET"
  ..$ url       : chr "https://datausa.io/api/data?&drilldowns=Nation&measures=Population"
  ..$ headers   : Named chr "application/json, text/xml, application/xml, */*"
  .. ..- attr(*, "names")= chr "Accept"
  ..$ fields    : NULL
  ..$ options   :List of 2
  .. ..$ useragent: chr "libcurl/8.6.0 r-curl/5.2.1 httr/1.4.7"
  .. ..$ httpget  : logi TRUE
  ..$ auth_token: NULL
  ..$ output    : list()
  .. ..- attr(*, "class")= chr [1:2] "write_memory" "write_function"
  ..- attr(*, "class")= chr "request"
 $ handle     :Class 'curl_handle' <externalptr> 
 - attr(*, "class")= chr "response"

You need to check one thing: the server status response.

response$status_code
[1] 200

200 means that your request was processed successfully. Introduce yourself to server status responses and learn a bit about them from the following source: HTTP response status codes.

The results is found in response$content, but…

class(response$content)
[1] "raw"
print(response$content[1:50])
 [1] 7b 22 64 61 74 61 22 3a 5b 7b 22 49 44 20 4e 61 74 69 6f 6e 22 3a 22 30 31 30 30 30
[29] 55 53 22 2c 22 4e 61 74 69 6f 6e 22 3a 22 55 6e 69 74 65 64 20 53

What is raw? It means that your data were obtained as raw binary data and they need to be decoded into an R character class representation. It is easy:

resp <- rawToChar(response$content)
class(resp)
[1] "character"

Is resp lengthy?

nchar(resp)
[1] 1764
cat(resp)
{"data":[{"ID Nation":"01000US","Nation":"United States","ID Year":2022,"Year":"2022","Population":331097593,"Slug Nation":"united-states"},{"ID Nation":"01000US","Nation":"United States","ID Year":2021,"Year":"2021","Population":329725481,"Slug Nation":"united-states"},{"ID Nation":"01000US","Nation":"United States","ID Year":2020,"Year":"2020","Population":326569308,"Slug Nation":"united-states"},{"ID Nation":"01000US","Nation":"United States","ID Year":2019,"Year":"2019","Population":324697795,"Slug Nation":"united-states"},{"ID Nation":"01000US","Nation":"United States","ID Year":2018,"Year":"2018","Population":322903030,"Slug Nation":"united-states"},{"ID Nation":"01000US","Nation":"United States","ID Year":2017,"Year":"2017","Population":321004407,"Slug Nation":"united-states"},{"ID Nation":"01000US","Nation":"United States","ID Year":2016,"Year":"2016","Population":318558162,"Slug Nation":"united-states"},{"ID Nation":"01000US","Nation":"United States","ID Year":2015,"Year":"2015","Population":316515021,"Slug Nation":"united-states"},{"ID Nation":"01000US","Nation":"United States","ID Year":2014,"Year":"2014","Population":314107084,"Slug Nation":"united-states"},{"ID Nation":"01000US","Nation":"United States","ID Year":2013,"Year":"2013","Population":311536594,"Slug Nation":"united-states"}],"source":[{"measures":["Population"],"annotations":{"source_name":"Census Bureau","source_description":"The American Community Survey (ACS) is conducted by the US Census and sent to a portion of the population every year.","dataset_name":"ACS 5-year Estimate","dataset_link":"http://www.census.gov/programs-surveys/acs/","table_id":"B01003","topic":"Diversity","subtopic":"Demographics"},"name":"acs_yg_total_population_5","substitutions":[]}]}

Now we can see that the API response is JSON indeed. To work with JSON in R, we need to convert it into some R known data structures. For example a list.

2. JSON

Understanding JSON

JSON (JavaScript Object Notation) is a lightweight data interchange format that is easy for humans to read and write, and easy for machines to parse and generate. It is widely used for data transmission between a server and a web application, as well as for storing and exchanging data in various contexts, including APIs.

Key Characteristics of JSON:

  • Human-readable: JSON is formatted in a way that is easy to understand for humans, making it ideal for data documentation and debugging.

  • Lightweight: It is a text format that is concise and easy to parse.

  • Language-independent: While derived from JavaScript, JSON is language-agnostic and can be used with most programming languages, including R.

JSON Structure:

JSON data is organized in two primary structures:

  1. Objects: An unordered set of key/value pairs. Each key is a string, and the value can be a string, number, object, array, true, false, or null. Objects are enclosed in curly braces {}.
    • Example:

      {
        "name": "Alice",
        "age": 30,
        "isStudent": false
      }
  2. Arrays: An ordered list of values. Values can be of any type (string, number, object, array, true, false, or null). Arrays are enclosed in square brackets [].
    • Example:

      [
        "apple",
        "banana",
        "cherry"
      ]

Using JSON in R

In R, you can use the jsonlite package to work with JSON data. The package provides functions to read JSON data from a file or URL and convert it into R data frames or lists, and vice versa.

  • Reading JSON Data:
print(response)
Response [https://datausa.io/api/data?&drilldowns=Nation&measures=Population]
  Date: 2024-06-02 12:39
  Status: 200
  Content-Type: application/json; charset=utf-8
  Size: 1.76 kB
print(resp)
[1] "{\"data\":[{\"ID Nation\":\"01000US\",\"Nation\":\"United States\",\"ID Year\":2022,\"Year\":\"2022\",\"Population\":331097593,\"Slug Nation\":\"united-states\"},{\"ID Nation\":\"01000US\",\"Nation\":\"United States\",\"ID Year\":2021,\"Year\":\"2021\",\"Population\":329725481,\"Slug Nation\":\"united-states\"},{\"ID Nation\":\"01000US\",\"Nation\":\"United States\",\"ID Year\":2020,\"Year\":\"2020\",\"Population\":326569308,\"Slug Nation\":\"united-states\"},{\"ID Nation\":\"01000US\",\"Nation\":\"United States\",\"ID Year\":2019,\"Year\":\"2019\",\"Population\":324697795,\"Slug Nation\":\"united-states\"},{\"ID Nation\":\"01000US\",\"Nation\":\"United States\",\"ID Year\":2018,\"Year\":\"2018\",\"Population\":322903030,\"Slug Nation\":\"united-states\"},{\"ID Nation\":\"01000US\",\"Nation\":\"United States\",\"ID Year\":2017,\"Year\":\"2017\",\"Population\":321004407,\"Slug Nation\":\"united-states\"},{\"ID Nation\":\"01000US\",\"Nation\":\"United States\",\"ID Year\":2016,\"Year\":\"2016\",\"Population\":318558162,\"Slug Nation\":\"united-states\"},{\"ID Nation\":\"01000US\",\"Nation\":\"United States\",\"ID Year\":2015,\"Year\":\"2015\",\"Population\":316515021,\"Slug Nation\":\"united-states\"},{\"ID Nation\":\"01000US\",\"Nation\":\"United States\",\"ID Year\":2014,\"Year\":\"2014\",\"Population\":314107084,\"Slug Nation\":\"united-states\"},{\"ID Nation\":\"01000US\",\"Nation\":\"United States\",\"ID Year\":2013,\"Year\":\"2013\",\"Population\":311536594,\"Slug Nation\":\"united-states\"}],\"source\":[{\"measures\":[\"Population\"],\"annotations\":{\"source_name\":\"Census Bureau\",\"source_description\":\"The American Community Survey (ACS) is conducted by the US Census and sent to a portion of the population every year.\",\"dataset_name\":\"ACS 5-year Estimate\",\"dataset_link\":\"http://www.census.gov/programs-surveys/acs/\",\"table_id\":\"B01003\",\"topic\":\"Diversity\",\"subtopic\":\"Demographics\"},\"name\":\"acs_yg_total_population_5\",\"substitutions\":[]}]}"
library(jsonlite)
data <- jsonlite::fromJSON(resp)

data is now a list:

data$source
data$data
  • Writing JSON Data:
json_data <- toJSON(data)
print(json_data)
{"data":[{"ID Nation":"01000US","Nation":"United States","ID Year":2022,"Year":"2022","Population":331097593,"Slug Nation":"united-states"},{"ID Nation":"01000US","Nation":"United States","ID Year":2021,"Year":"2021","Population":329725481,"Slug Nation":"united-states"},{"ID Nation":"01000US","Nation":"United States","ID Year":2020,"Year":"2020","Population":326569308,"Slug Nation":"united-states"},{"ID Nation":"01000US","Nation":"United States","ID Year":2019,"Year":"2019","Population":324697795,"Slug Nation":"united-states"},{"ID Nation":"01000US","Nation":"United States","ID Year":2018,"Year":"2018","Population":322903030,"Slug Nation":"united-states"},{"ID Nation":"01000US","Nation":"United States","ID Year":2017,"Year":"2017","Population":321004407,"Slug Nation":"united-states"},{"ID Nation":"01000US","Nation":"United States","ID Year":2016,"Year":"2016","Population":318558162,"Slug Nation":"united-states"},{"ID Nation":"01000US","Nation":"United States","ID Year":2015,"Year":"2015","Population":316515021,"Slug Nation":"united-states"},{"ID Nation":"01000US","Nation":"United States","ID Year":2014,"Year":"2014","Population":314107084,"Slug Nation":"united-states"},{"ID Nation":"01000US","Nation":"United States","ID Year":2013,"Year":"2013","Population":311536594,"Slug Nation":"united-states"}],"source":[{"measures":["Population"],"annotations":{"source_name":"Census Bureau","source_description":"The American Community Survey (ACS) is conducted by the US Census and sent to a portion of the population every year.","dataset_name":"ACS 5-year Estimate","dataset_link":"http://www.census.gov/programs-surveys/acs/","table_id":"B01003","topic":"Diversity","subtopic":"Demographics"},"name":"acs_yg_total_population_5","substitutions":[]}]} 
# write(json_data, file = "data.json")

Understanding JSON is essential for advanced analytics, as it enables seamless integration with web APIs and efficient data handling in your R projects.

Let’s plot the time series of the US population over years then:

library(ggplot2)
library(ggrepel)
ggplot(data = data$data, 
       aes(x = Year,
           y = Population, 
           label = Population)) + 
  geom_path(size = .25, color = "blue", group = 1) + 
  geom_point(size = 2, color = "blue") + 
  geom_label_repel(size = 3) + 
  ggtitle("US Population") +
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  theme(plot.title = element_text(hjust = .5))
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
ℹ Please use `linewidth` instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.

Make another API call and inspect the data

For each API that you want to use you will need to read its documentation and learn about the parameters that you may pass to it.

I have stripped this API call from https://datausa.io/profile/soc/education-legal-community-service-arts-media-occupations.

You can copy and paste the entire API call into your browsers navigation bar to obtain the JSON response directly.

The data are on education, legal, community service, arts, & media occupations in the USA.

Make a call and check the server response status:

api_call <- paste0(baseEndPoint, 
                   "?", 
                   paste("PUMS Occupation=210000-270000", 
                         "measure=Total Population,Total Population MOE Appx,Record Count",
                         "drilldowns=Wage Bin",
                         "Workforce Status=true",
                         "Record Count>=5", 
                         sep = "&"))
response <- GET(URLencode(api_call))
response$status
[1] 200

Convert the response to JSON and than to list and a data.frame:

response <- rawToChar(response$content)
response <- fromJSON(response)
data <- response$data
head(data)

Visualize with {ggplot2}:

data$`Wage Bin` <- factor(data$`Wage Bin`, 
                          levels = unique(data$`Wage Bin`))
ggplot(data = data, 
       aes(x = Year,
           y = log(`Total Population`), 
           color = `Wage Bin`,
           fill = `Wage Bin`)) + 
  geom_path(size = 1.5, group = 1) + 
  geom_point(size = 13) + 
  facet_wrap(~`Wage Bin`, ncol = 2) +
  ggtitle("US: Education, legal, community service, arts, & media occupations") +
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  theme(plot.title = element_text(hjust = .5, size = 70)) + 
  theme(axis.text.x = element_text(angle = 90, size = 40)) +
  theme(axis.title.x = element_text(size = 50)) + 
  theme(axis.text.y = element_text(size = 40)) +
  theme(axis.title.y = element_text(size = 50)) + 
  theme(legend.text = element_text(size = 50)) +
  theme(legend.title = element_text(size = 50)) +
  theme(strip.text = element_text(size = 45)) +
  theme(strip.background = element_blank()) +
  theme(legend.position = "top")

3. Complicated API responses and XML

Wikidata is a free, collaborative, multilingual knowledge base that stores structured data to support Wikipedia and other Wikimedia Foundation projects. It serves as a central repository for data, enabling easy access and reuse of information across various platforms and applications.

Key Features:

  • Centralized Data: Provides a single source of truth for structured data, ensuring consistency across multiple Wikimedia projects.
  • Collaborative: Anyone can contribute, edit, and update entries, similar to Wikipedia.
  • Multilingual: Supports data entries in multiple languages, making it accessible to a global audience.
  • Linked Data: Integrates with other databases and knowledge graphs, enhancing data connectivity and utility.

In the following examples we will be using the Wikibase API to obtain data from Wikidata, the World’s largest open knowledge base that comprises all structured information from Wikipedia and many other sources.

library(XML) # - parse XML format

David Bowie in Wikidata

We will contact the Wikibase API to obtain all data stored in Wikidata on David Bowie (who has a Q identifier of Q5383 in this knowledge base). We will ask the Wikibase API to use JSON to describe its response. Here is how the JSON response will look like: Wikibase API response.

query <- 
  'https://www.wikidata.org/w/api.php?action=wbgetentities&ids=Q5383&languages=en&format=json'
response <- GET(URLencode(query))
response <- rawToChar(response$content)
response <- fromJSON(response)
class(response)
[1] "list"

Now, Wikidata is very complex (and thus very powerful as a descriptive system; after all, it’s goal is to be able to describe just anything that we can imagine, talk, and write about), so what fromJSON() returns is a nasty, nasty, nested list:

instaceOf_DavidBowie <- response$entities$Q5383$claims$P31
instaceOf_DavidBowie$mainsnak$datavalue$value

It is necessary to study the Wikidata DataModel carefully in order to be able to navigate the knowledge structures that it describes:

labelOf_DavidBowie <- response$entities$Q5383$labels$en$value
labelOf_DavidBowie
[1] "David Bowie"

However, once you do learn about Wikidata’s data model… Tens of millions of highly structured items and relations among them will become accessible to you. Order emerges from chaos in this case, I assure you. Besides JSON, there is XML (and many more, but we will focus on these two formats).

An XML response from a REST API

Understanding XML

XML (eXtensible Markup Language) is a versatile text-based format used for representing structured data. It is designed to be both human-readable and machine-readable, making it a popular choice for data interchange between systems.

Key Characteristics of XML:

  • Self-descriptive: XML uses tags to define data elements, providing context and structure.
  • Hierarchical: Data is organized in a tree-like structure, allowing for complex nested relationships.
  • Flexible: You can define your own tags, making XML highly adaptable to different data needs.
  • Cross-platform: XML can be used and parsed on any operating system or programming language.

XML Structure:

XML documents consist of elements enclosed in tags, with a root element that contains all other elements.

  • Example of a Simple XML Document:

    <person>
      <name>John Doe</name>
      <age>30</age>
      <email>john.doe@example.com</email>
    </person>
  • Example with Nested Elements:

    <bookstore>
      <book>
        <title>Effective R Programming</title>
        <author>Jane Smith</author>
        <price>29.99</price>
      </book>
      <book>
        <title>Data Science with R</title>
        <author>John Doe</author>
        <price>39.99</price>
      </book>
    </bookstore>

Let’s get back to the Wikibase API and ask for the same data on David Bowie wrapped in an XML response:

query <- 
  'https://www.wikidata.org/w/api.php?action=wbgetentities&ids=Q5383&languages=en&format=xml'
response <- GET(URLencode(query))
response <- rawToChar(response$content)
response <- xmlParse(response)
response <- xmlToList(response)

Note: format=xml.

The English label for David Bowie in Wikidata:

response$entities$entity$labels$label
     language         value 
         "en" "David Bowie" 
class(response$entities$entity$labels$label)
[1] "character"

4.4 All names of David Bowie

Now, David Bowie, in all languages available in Wikidata. First, we get the data.

query <- 
  'https://www.wikidata.org/w/api.php?action=wbgetentities&ids=Q5383&format=json'
response <- GET(URLencode(query))
response <- rawToChar(response$content)
response <- fromJSON(response)

Second: study the structure of the response, and then lapply() across the appropriate set of lists:

labels <- lapply(response$entities$Q5383$labels, function(x) {
  paste0(x$value, " (", x$language, ")")
})
labels <- paste(labels, collapse = ", ")
print(labels)
[1] "David Bowie (fr), David Bowie (de), David Bowie (en-ca), David Bowie (en-gb), ديفيد بوي (ar), Devid Boui (az), Дэвід Боўі (be), Дейвид Боуи (bg), David Bowie (br), David Bowie (bs), David Bowie (ca), David Bowie (co), David Bowie (cs), David Bowie (cy), David Bowie (da), David Bowie (diq), Ντέιβιντ Μπόουι (el), David Bowie (eo), David Bowie (es), David Bowie (et), David Bowie (eu), دیوید بویی (fa), David Bowie (fi), David Bowie (ga), David Bowie (gl), דייוויד בואי (he), डेविड बोवी (hi), David Bowie (hr), David Bowie (hu), David Bowie (id), David Bowie (io), David Bowie (is), David Bowie (it), デヴィッド・ボウイ (ja), David Bowie (jv), დევიდ ბოუი (ka), 데이비드 보위 (ko), David Bowie (li), David Bowie (lt), Deivids Bovijs (lv), Дејвид Боуви (mk), David Bowie (nl), David Bowie (nn), David Bowie (oc), David Bowie (pl), David Bowie (pms), David Bowie (pt), David Bowie (pt-br), David Bowie (ro), Дэвид Боуи (ru), David Bowie (scn), David Bowie (sh), David Bowie (sk), David Bowie (sl), David Bowie (sq), Дејвид Боуи (sr), David Bowie (sv), డేవిడ్ బౌవీ (te), เดวิด โบอี (th), David Bowie (tr), Девід Бові (uk), David Bowie (uz), David Bowie (vi), David Bowie (vls), 大卫·鲍伊 (zh), 大衛寶兒 (yue), David Bowie (de-ch), David Bowie (qu), David Bowie (la), Дэйвід Боўі (be-tarask), David Bowie (nb), David Bowie (mg), Դեյվիդ Բոուի (hy), David Bowie (ast), David Bowie (sco), David Bowie (lb), Дэвид Боуи (kk), David Bowie (ia), David Bowie (sd), David Bowie (gsw), डेविड बोवी (bho), David Bowie (fo), David Bowie (hsb), ਡੇਵਿਡ ਬੋਵੀ (pa), David Bowie (szl), David Bowie (af), David Bowie (an), David Bowie (bar), David Bowie (de-at), David Bowie (frp), David Bowie (fur), David Bowie (gd), David Bowie (ie), David Bowie (kg), David Bowie (lij), David Bowie (min), David Bowie (ms), David Bowie (nap), David Bowie (nds), David Bowie (nds-nl), David Bowie (nrm), David Bowie (pcd), David Bowie (rm), David Bowie (sc), David Bowie (sr-el), David Bowie (sw), David Bowie (vec), David Bowie (vo), David Bowie (wa), David Bowie (wo), David Bowie (zu), دیوید بویی (azb), ডেভিড বোয়ি (bn), David Bowie (eml), David Bowie (bcl), دەیڤد بویی (ckb), David Bowie (fy), David Bowie (lmo), David Bowie (nah), Devid Bowi (tt), Боуи Дэвид (cv), ഡേവിഡ് ബോയി (ml), David Bowie (nan), David Bowie (war), David Bowie (ilo), Дэвид Боуи (ba), 大衛寶兒 (zh-hk), 大卫·鲍伊 (zh-cn), 大卫·鲍伊 (zh-hans), 大衛·鮑伊 (zh-hant), 大衛·鮑伊 (zh-mo), 大卫·鲍伊 (zh-my), 大卫·鲍威 (zh-sg), 大衛·鮑伊 (zh-tw), დევიდ ბოუი (xmf), David Bowie (ext), ديفيد باوى (arz), David Bowie (lfn), டேவிட் போவி (ta), ڈیوڈ بوئی (ur), David Bowie (en), 大卫·鲍伊 (wuu), David Bowie (vro), ዴቪድ ቦሊ (am), Տէյվիտ Պոուի (hyw), David Bowie (kl), David Bowie (tl), David Bowie (smn), David Bowie (dag), David Bowie (kw), David Bowie (ku), David Bowie (tw), David Bowie (sje), David Bowie (jut), David Bowie (mos), David Bowie (ha), David Bowie (no), David Bowie (en-us)"

Didn’t I tell you how lists and functional programming are important in R?

4. Parse PDF files from R

Sooner or later you will want to extract tabular data from PDF files…

# Load the tabulizer library
# Download Download the Microsoft Build of OpenJDK
# https://learn.microsoft.com/en-us/java/openjdk/download
# Find your JAVA_HOME (to be exemplified in the session) and then:
# Sys.setenv(JAVA_HOME="C:/Program Files/Microsoft/jdk-11.0.23.9-hotspot")
library(tabulapdf)

# Define the path to the PDF file
data_dir <- paste0(getwd(), "/_data/")
pdf_path <- paste0(data_dir, "mtcars.pdf")

# Extract all tables from the PDF
tables <- tabulapdf::extract_tables(pdf_path)

Returned a list tables:

tables
[[1]]

[[2]]

[[3]]
NA

Extract to data.frame, one by one:

mtcars <- tables[[1]]
iris <- tables[[2]]
bio_data <- tables[[3]]

5. Case Study: World Bank Data

  • Learn about the WDI package - World Bank data in R - install it, and learn how to use it: WDI
  • Here’s some code to get you started:
# install.packages("WDI)
library(WDI)
WDIsearch('gdp')
data_set = WDI(indicator='NY.GDP.MKTP.CD', 
               country=c('US'), 
               start=1960, end=2022)
print(data_set)

You will need to learn about The World Bank Data in order to understand the indicators!

data_set = WDI(indicator='NY.GDP.MKTP.CD', 
               country=c('RS', 'HR', 'SI', 'ME', 'BA', 'MK'), 
               start=1990, 
               end=2023)
print(data_set)
ggplot(data_set, 
       aes(x = year, 
           y = NY.GDP.MKTP.CD, 
           color = country)) + 
  geom_line() +
  xlab('Year') + ylab('GDP per capita') + 
  theme_bw() + 
  theme(panel.border = element_blank())
Warning: Removed 35 rows containing missing values or values outside the scale range
(`geom_line()`).

Your task is to provide an EDA based on World Bank Data on the how ex-Yu countries developed after 1990. Use ggplot2 and Plotly for visualizations. Study any World Bank Data indicators that you might find interesting for the comparative study at hand, obtained them via the WDI package, cross-tabulate against each other if necessary or interesting, and provide your insights in R Markdown.

6. R Data Ecosystem

First, have a look at this treasure: A list of over 1,000 datasets available in R packages

Now, here is a list of some R packages specifically designed to connect to third-party open-data resources and provide data.frames:

  1. WDI: Accesses World Bank data.
  2. tidycensus: Accesses US Census Bureau data, including the American Community Survey.
  3. rnaturalearth: Downloads country boundaries and other natural earth map data.
  4. Eurostat: Accesses data from the Eurostat database.
  5. quandl: Accesses financial, economic, and alternative datasets from Quandl.
  6. faoapi: Accesses data from the FAO (Food and Agriculture Organization) database.
  7. rnoaa: Accesses data from the National Oceanic and Atmospheric Administration (NOAA).
  8. tuber: Accesses YouTube API for video and channel data.
  9. imfr: Accesses data from the International Monetary Fund (IMF) database.
  10. rdhs: Accesses Demographic and Health Surveys (DHS) Program data.
  11. blscrapeR: Accesses data from the Bureau of Labor Statistics (BLS).
  12. rWBclimate: Accesses World Bank Climate Data API.

Important sources, documentation, etc.

R Markdown

R Markdown is what I have used to produce this beautiful Notebook. We will learn more about it near the end of the course, but if you already feel ready to dive deep, here’s a book: R Markdown: The Definitive Guide, Yihui Xie, J. J. Allaire, Garrett Grolemunds.


Goran S. Milovanović

DataKolektiv, 2024.

contact:


License: GPLv3 This Notebook is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This Notebook is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this Notebook. If not, see http://www.gnu.org/licenses/.


LS0tCnRpdGxlOiAiQURWQU5DRUQgQU5BTFlTVCAtIEZvdW5kYXRpb25zIGZvciBBZHZhbmNlZCBEYXRhIEFuYWx5dGljcyBpbiBSIC0gU2Vzc2lvbjA3IgphdXRob3I6Ci0gbmFtZTogR29yYW4gUy4gTWlsb3Zhbm92acSHLCBQaEQKICBhZmZpbGlhdGlvbjogRGF0YUtvbGVrdGl2LCBDaGllZiBTY2llbnRpc3QgJiBPd25lcjsgTGVhZCBEYXRhIFNjaWVudGlzdCBmb3Igc21hcnRvY3RvCmFic3RyYWN0OiBudWxsCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICB0aGVtZTogc3BhY2VsYWIKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICB0b2NfZGVwdGg6IDUKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZGVwdGg6IDUKICBwZGZfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogJzUnCi0tLQoKIVtdKF9pbWcvREtfTG9nb18xMDAucG5nKQoKKioqCiMgTW9kdWxlIDQ6IENvbGxlY3RpbmcgZGF0YSBmcm9tIEFQSXMgYW5kIFBERnMsIGF1dG9tYXRlZCBwcm9kdWN0aW9uIGluIE1TIE9mZmljZSBmcm9tIFIsIGFuZCBPcGVuQUkgQ2hhdEdQVCBmcm9tIFIKCiMjIFdlZWsgMDc6IFIgZGF0YSBlY29zeXN0ZW06IHBhY2thZ2VzIGZvciBhY2Nlc3NpbmcgdmFyaW91cyBkYXRhIHNvdXJjZXMuIFVuZGVyc3RhbmRpbmcgSlNPTiBhbmQgWE1MIGZvciBBUEkgY2FsbHMuIFBhcnNlIFBERiBmaWxlcy4KCiAKIyMjIFdoYXQgZG8gd2Ugd2FudCB0byBkbyB0b2RheT8KCkluIHRoaXMgc2Vzc2lvbiwgd2UgZGVsdmUgaW50byB0aGUgcHJhY3RpY2FsaXRpZXMgb2YgaW1wb3J0aW5nIGFuZCBwcm9jZXNzaW5nIGRhdGEgZnJvbSB2YXJpb3VzIHNvdXJjZXMgdXNpbmcgUiwgYSBjcnVjaWFsIHNraWxsIGZvciBhbnkgZGF0YSBhbmFseXN0LiBPdXIgZXhwbG9yYXRpb24gc3RhcnRzIHdpdGggbWFraW5nIHNpbXBsZSBBUEkgY2FsbHMgdG8gZmV0Y2ggZGF0YSwgcHJvdmlkaW5nIGEgZm91bmRhdGlvbiBmb3IgdW5kZXJzdGFuZGluZyB0aGUgc3RydWN0dXJlIGFuZCBmb3JtYXQgb2YgSlNPTiBhbmQgWE1MIGRhdGEuIFdlIHdpbGwgdGhlbiBhZHZhbmNlIHRvIG1vcmUgY29tcGxleCBBUEkgaW50ZXJhY3Rpb25zIGFuZCBkYXRhIHByb2Nlc3NpbmcgdGVjaG5pcXVlcywgZXF1aXBwaW5nIHlvdSB3aXRoIHRoZSBza2lsbHMgdG8gaGFuZGxlIHJlYWwtd29ybGQgZGF0YSBleHRyYWN0aW9uIHRhc2tzIGVmZmljaWVudGx5LiBBZGRpdGlvbmFsbHksIHdlJ2xsIGNvdmVyIG1ldGhvZHMgZm9yIGV4dHJhY3RpbmcgZGF0YSBmcm9tIFBERiBmaWxlcywgYW4gZXNzZW50aWFsIGNhcGFiaWxpdHkgZm9yIGRlYWxpbmcgd2l0aCB1bnN0cnVjdHVyZWQgZGF0YS4gVGhlIHNlc3Npb24gZW5kcyB3aXRoIGEgY29tcHJlaGVuc2l2ZSBjYXNlIHN0dWR5IHV0aWxpemluZyBXb3JsZCBCYW5rIGRhdGEsIHdoZXJlIHdlJ2xsIGFwcGx5IG91ciBsZWFybmVkIHRlY2huaXF1ZXMgdG8gYW5hbHl6ZSBhbmQgcmVwb3J0IG9uIGdsb2JhbCBlY29ub21pYyBpbmRpY2F0b3JzLgoKKipGZWVkYmFjayoqIHNob3VsZCBiZSBzZW5kIHRvIGBnb3Jhbi5taWxvdmFub3ZpY0BkYXRha29sZWt0aXYuY29tYC4gClRoZXNlIG5vdGVib29rcyBhY2NvbXBhbnkgdGhlIEFEVkFOQ0VEIEFOQUxZU1QgLSBGb3VuZGF0aW9ucyBmb3IgQWR2YW5jZWQgRGF0YSBBbmFseXRpY3MgaW4gUiBbRGF0YUtvbGVrdGl2XShodHRwOi8vd3d3LmRhdGFrb2xla3Rpdi5jb20vYXBwX2RpcmVjdC9EYXRhS29sZWt0aXZTZXJ2ZXIvKSB0cmFpbmluZy4KCioqKgoKIyMjIFdlbGNvbWUgdG8gUiEKCiFbXShfaW1nL0FkdkFuYWx5dGljc1IyMDI0X0Jhbm5lci5qcGVnKQoKIyMjIDEuIFVuZGVyc3RhbmRpbmcsIG1ha2luZywgYW5kIHBhcnNpbmcgQVBJIGNhbGxzCgpVbnRpbCBub3csIHdlIGhhdmUgdXNlZCBvbmx5IGRhdGEgc3RvcmVkIGFzIGAuY3N2YCBvciBgLnhsc3hgIGZpbGVzLiBBcyB3ZSBoYXZlIHNlZW4gaXQgd2FzIHBvc3NpYmxlIHRvIG1hcCBzdWNoIGZpbGVzIDE6MSBvbnRvIGRhdGFmcmFtZXMgaW4gUjogY29sdW1ucyB3ZXJlIGRlZmluZWQsIHdpdGggdGhlIGZpcnN0IHJvd3MgaG9sZGluZyBjb2x1bW4gbmFtZXMgYnkgY29udmVudGlvbi4uLiBIb3dldmVyLCBpbiBEYXRhIFNjaWVuY2UsIG1hbnkgdGltZXMgd2UgZmFjZSB0aGUgc2l0dWF0aW9uIGluIHdoaWNoIHdlIGhhdmUgdG8gb2J0YWluIHNvbWUgZGF0YSBmcm9tIGEgc291cmNlIHRoYXQgZG9lcyBub3QgbmVjZXNzYXJpbHkgZGVsaXZlciAqInRhYnVsYXIiKiBkYXRhIHN0cnVjdHVyZXMuIEFsc28sIG1hbnkgdGltZXMgd2Ugd2lsbCBiZSBmYWNpbmcgZGF0YSBzdHJ1Y3R1cmVzIHRoYXQgYXJlIGVzc2VudGlhbGx5IG5vdCAidGFidWxhciI6IGZvciBleGFtcGxlLCBkYXRhIHN0cnVjdHVyZXMgaW4gd2hpY2ggc29tZSBlbGVtZW50cyBjb250YWluIGNlcnRhaW4gZmllbGRzIHdoaWNoIG90aGVyIGVsZW1lbnRzIGRvIG5vdCAtIGFuZCBub3QgYmVjYXVzZSB0aGVyZSBhcmUgbWlzc2luZyBkYXRhLCBidXQgYmVjYXVzZSBzb21ldGltZXMgaXQgZG9lcyBub3QgbWFrZSBzZW5zZSBmb3Igc29tZXRoaW5nIHRvIGJlIGRlc2NyaWJlZCBieSBhIGNlcnRhaW4gYXR0cmlidXRlIGF0IGFsbC4gSW1hZ2luZSBkZXNjcmliaW5nIFtEYXZpZCBCb3dpZV0oaHR0cHM6Ly93d3cud2lraWRhdGEub3JnL3dpa2kvUTUzODMpLCBhIGZhbW91cyBFbmdsaXNoIG11c2ljaWFuLCBieSBhIGRhdGEgc3RydWN0dXJlOiB3ZSBjYW4ga25vdyBoaXMgZGF0ZSBvZiBiaXJ0aCwgYW5kIHRoZSByZWxlYXNlIGRhdGVzIG9mIGhpcyBhbGJ1bXMgcGVyaGFwcywgYnV0IHNob3VsZCBiZSBwbGFjZSBhbGwgdGhhdCBkYXRhIGludG8gb25lIHNpbmdsZSBjb2x1bW4/IE5vLCBiZWNhdXNlIHRoZSBzZW1hbnRpY3Mgb2Ygc3VjaCBhIGZpZWxkIHdvdWxkIGJlIHdlaXJkLCBvZiBjb3Vyc2UuIEhvdyB3b3VsZCB3ZSBvcmdhbml6ZSB0aGUgcm93cyBpbiBhIGRhdGFmcmFtZSBkZXNjcmliaW5nIERhdmlkIEJvd2llOiB0aGUgZmlyc3Qgcm93IGRlc2NyaWJlcyB0aGUgcGVyc29uLCB3aGlsZSBvdGhlciByb3dzIGRlc2NyaWJlIGhpcyB3b3JrcyBvZiBhcnQ/IFNvLCB3ZSBoYXZlIGEgY29sdW1uIGZvciBgZGF0ZU9mQmlydGhgLCBhbmQgdGhhdCBjb2x1bW4gaGFzIGEgdmFsdWUgaW4gdGhlIGZpcnN0IHJvdyBvZiB0aGUgZGF0YWZyYW1lIG9ubHksIGFuZCB0aGVuIHdlIGhhdmUgYSBjb2x1bW4gZm9yIGByZWxlYXNlRGF0ZWAsIGFuZCB0aGF0IGNvbHVtbiBob2xkcyBgTkFgIGluIHRoZSBmaXJzdCByb3cgYW5kIHRoZW4gYSB0aW1lc3RhbXAgaW4gYWxsIG90aGVyIHJvd3MgdGhhdCByZWZlciB0byBoaXMgYWxidW1zPyBXYWl0LCB3aGF0IGFib3V0IGhpcyBzcG91c2UsIGNoaWxkcmVuLCBjb2xsYWJvcmF0b3JzOiBhc3NpZ24gYSByb3cgaW4gYSBkYXRhZnJhbWUgdG8gZWFjaCBvbmU/CgpOby4gT2YgY291cnNlLCBhICoqbGlzdCoqIGluIFIgd291bGQgZG8sIGNvcnJlY3Q/CgpJbiB0aGUgZm9sbG93aW5nIGV4YW1wbGUgd2Ugd2lsbCBhY2Nlc3MgYSBmcmVlIFJFU1QgQVBJIGZyb20gd2l0aGluIG91ciBSIGVudmlyb25tZW50LCBjb2xsZWN0IHRoZSBBUEkgcmVzcG9uc2UgYXMgW0pTT05dKGh0dHBzOi8vd3d3Lmpzb24ub3JnL2pzb24tZW4uaHRtbCksIGNvbnZlcnQgaXQgdG8gYW4gUiBsaXN0LCBhbmQgcGxheSB3aXRoIHRoZSBkYXRhLgoKIyMjIyBTZXR1cCB0aGUgYmFzaWMgQVBJIGFjY2VzcyBwYXJhbWV0ZXJzCgpJbiB0aGlzIGV4YW1wbGUgd2Ugd2lsbCByZWx5IG9uIHRoZSBmcmVlIFtodHRwczovL2RhdGF1c2EuaW8vXShodHRwczovL2RhdGF1c2EuaW8vKSBBUEkgdG8gb2J0YWluIHN0YXRpc3RpY2FsIGRhdGEuIEhlcmUgaXMgdGhlIGludHJvIHRvIHRoZWlyIEFQSTogW2RhdGF1c2EuaW8gQVBJXShodHRwczovL2RhdGF1c2EuaW8vYWJvdXQvYXBpLykuCgotIFlvdSB3aWxsIGZpbmQgdGhlIEFQSSBiYXNlIGVuZHBvaW50IHRoZXJlOgoKYGBge3IgZWNobyA9IFR9CmJhc2VFbmRQb2ludCA8LSAiaHR0cHM6Ly9kYXRhdXNhLmlvL2FwaS9kYXRhIgpgYGAKCiMjIyMgTWFrZSBhIHNpbXBsZSBBUEkgY2FsbAoKV2Ugd2lsbCB1c2UgW3todHRyfV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2h0dHIvdmlnbmV0dGVzL3F1aWNrc3RhcnQuaHRtbCkgdG8gZ2V0IGluIHRvdWNoIHdpdGggdGhlIEFQSS4gSXQgaXMgYSBwYXJ0IG9mIFt7dGlkeXZlcnNlfV0oaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy8pLgoKYGBge3IgZWNobyA9IFR9CmxpYnJhcnkoaHR0cikKYGBgCgoqKlN0ZXAgMS4gRGVmaW5lIEFQSSBwYXJhbWV0ZXJzLioqCgpGaXJzdCB3ZSBkZWZpbmUgdGhlIEFQSSBwYXJhbWV0ZXJzLgoKYGBge3IgZWNobyA9IFR9CiMjIyAtLS0gY29tcG9zZSBBUEkgY2FsbAojIC0gdXNlIGJhc2UgQVBJIGVuZHBvaW50CiMgLSBhbmQgY29uY2F0ZW5hdGUgd2l0aCBBUEkgcGFyYW1ldGVycwojIC0gZnJvbSB0aGUgZm9sbG93aW5nIGV4YW1wbGU6IGh0dHBzOi8vZGF0YXVzYS5pby9hYm91dC9hcGkvCiMgLSBwYXJhbWV0ZXI6IGRyaWxsZG93bnMKZHJpbGxkb3ducyA8LSBwYXN0ZTAoImRyaWxsZG93bnM9IiwgIk5hdGlvbiIpCiMgLSBwYXJhbWV0ZXI6IG1lYXN1cmVzCm1lYXN1cmVzIDwtIHBhc3RlMCgibWVhc3VyZXM9IiwgIlBvcHVsYXRpb24iKQojIC0gcGFyYW1ldGVyczoKcGFyYW1zIDwtIHBhc3RlKCImIiwgYyhkcmlsbGRvd25zLCBtZWFzdXJlcyksCiAgICAgICAgICAgICAgICBzZXAgPSAiIiwgY29sbGFwc2UgPSAiIikKY2F0KHBhcmFtcykKYGBgCgoqKlN0ZXAgMi4gQ29tcG9zZSBBUEkgY2FsbC4qKgoKV2UgcHV0IHRvZ2V0aGVyIHRoZSBgYmFzZUVuZFBvaW50YCB3aXRoIHRoZSBBUEkgY2FsbCBwYXJhbWV0ZXJzOgoKYGBge3IgZWNobyA9IFR9CmFwaV9jYWxsIDwtIHBhc3RlMChiYXNlRW5kUG9pbnQsICI/IiwgcGFyYW1zKQpjYXQoYXBpX2NhbGwpCmBgYAoKKipTdGVwIDMuIE1ha2UgQVBJIGNhbGwuKioKCldlIHVzZSBgaHR0cjo6R0VUKClgIHRvIGNvbnRhY3QgdGhlIEFQSSwgYXNrIGZvciBkYXRhLCBhbmQgZmV0Y2ggdGhlIHJlc3VsdDoKCmBgYHtyIGVjaG8gPSBUfQpyZXNwb25zZSA8LSBodHRyOjpHRVQoVVJMZW5jb2RlKGFwaV9jYWxsKSkKY2xhc3MocmVzcG9uc2UpCmBgYAoKVGhlIGBVUkxlbmNvZGUoYXBpX2NhbGwpYCBjYWxsIHRvIHRoZSBiYXNlIFIgYFVSTGVuY29kZSgpYCBmdW5jdGlvbiB3aWxsIHRha2UgY2FyZSBvZiBbUGVyY2VudC1lbmNvZGluZ10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvUGVyY2VudC1lbmNvZGluZykgd2hlcmUgYW5kIGlmIG5lY2Vzc2FyeS4gSGludDogYWx3YXlzIHVzZSBgVVJMZW5jb2RlKHlvdXJfYXBpX2NhbGwpYC4KCldlIGNhbiBzZWUgdGhhdCBgcmVzcG9uc2VgIGlzIG5vdyBvZiBhIGByZXNwb25zZWAgY2xhc3MuIEl0IGlzIHByZXR0eSBzdHJ1Y3R1cmVkIGFuZCByaWNoIGluZGVlZDoKCmBgYHtyIGVjaG8gPSBUfQpzdHIocmVzcG9uc2UpCmBgYAoKWW91IG5lZWQgdG8gY2hlY2sgb25lIHRoaW5nOiB0aGUgc2VydmVyIHN0YXR1cyByZXNwb25zZS4KCmBgYHtyIGVjaG8gPSBUfQpyZXNwb25zZSRzdGF0dXNfY29kZQpgYGAKCmAyMDBgIG1lYW5zIHRoYXQgeW91ciByZXF1ZXN0IHdhcyBwcm9jZXNzZWQgc3VjY2Vzc2Z1bGx5LiBJbnRyb2R1Y2UgeW91cnNlbGYgdG8gc2VydmVyIHN0YXR1cyByZXNwb25zZXMgYW5kIGxlYXJuIGEgYml0IGFib3V0IHRoZW0gZnJvbSB0aGUgZm9sbG93aW5nIHNvdXJjZTogW0hUVFAgcmVzcG9uc2Ugc3RhdHVzIGNvZGVzXShodHRwczovL2RldmVsb3Blci5tb3ppbGxhLm9yZy9lbi1VUy9kb2NzL1dlYi9IVFRQL1N0YXR1cykuCgpUaGUgcmVzdWx0cyBpcyBmb3VuZCBpbiBgcmVzcG9uc2UkY29udGVudGAsIGJ1dC4uLgoKYGBge3IgZWNobyA9IFR9CmNsYXNzKHJlc3BvbnNlJGNvbnRlbnQpCmBgYAoKYGBge3IgZWNobyA9IFR9CnByaW50KHJlc3BvbnNlJGNvbnRlbnRbMTo1MF0pCmBgYAoKV2hhdCBpcyBgcmF3YD8gSXQgbWVhbnMgdGhhdCB5b3VyIGRhdGEgd2VyZSBvYnRhaW5lZCBhcyAqcmF3IGJpbmFyeSBkYXRhKiBhbmQgdGhleSBuZWVkIHRvIGJlIGRlY29kZWQgaW50byBhbiBSIGBjaGFyYWN0ZXJgIGNsYXNzIHJlcHJlc2VudGF0aW9uLiBJdCBpcyBlYXN5OgoKYGBge3IgZWNobyA9IFR9CnJlc3AgPC0gcmF3VG9DaGFyKHJlc3BvbnNlJGNvbnRlbnQpCmNsYXNzKHJlc3ApCmBgYAoKSXMgYHJlc3BgIGxlbmd0aHk/CgpgYGB7ciBlY2hvID0gVH0KbmNoYXIocmVzcCkKYGBgCgpgYGB7ciBlY2hvID0gVH0KY2F0KHJlc3ApCmBgYAoKTm93IHdlIGNhbiBzZWUgdGhhdCB0aGUgQVBJIHJlc3BvbnNlIGlzICoqSlNPTioqIGluZGVlZC4gVG8gd29yayB3aXRoIEpTT04gaW4gUiwgd2UgbmVlZCB0byBjb252ZXJ0IGl0IGludG8gc29tZSBSIGtub3duIGRhdGEgc3RydWN0dXJlcy4gRm9yIGV4YW1wbGUgYSBsaXN0LgoKIyMjIDIuIEpTT04KCiMjIyMgVW5kZXJzdGFuZGluZyBKU09OCgoqKkpTT04gKEphdmFTY3JpcHQgT2JqZWN0IE5vdGF0aW9uKSoqIGlzIGEgbGlnaHR3ZWlnaHQgZGF0YSBpbnRlcmNoYW5nZSBmb3JtYXQgdGhhdCBpcyBlYXN5IGZvciBodW1hbnMgdG8gcmVhZCBhbmQgd3JpdGUsIGFuZCBlYXN5IGZvciBtYWNoaW5lcyB0byBwYXJzZSBhbmQgZ2VuZXJhdGUuIEl0IGlzIHdpZGVseSB1c2VkIGZvciBkYXRhIHRyYW5zbWlzc2lvbiBiZXR3ZWVuIGEgc2VydmVyIGFuZCBhIHdlYiBhcHBsaWNhdGlvbiwgYXMgd2VsbCBhcyBmb3Igc3RvcmluZyBhbmQgZXhjaGFuZ2luZyBkYXRhIGluIHZhcmlvdXMgY29udGV4dHMsIGluY2x1ZGluZyBBUElzLgoKIyMjIyBLZXkgQ2hhcmFjdGVyaXN0aWNzIG9mIEpTT046CgotICoqSHVtYW4tcmVhZGFibGUqKjogSlNPTiBpcyBmb3JtYXR0ZWQgaW4gYSB3YXkgdGhhdCBpcyBlYXN5IHRvIHVuZGVyc3RhbmQgZm9yIGh1bWFucywgbWFraW5nIGl0IGlkZWFsIGZvciBkYXRhIGRvY3VtZW50YXRpb24gYW5kIGRlYnVnZ2luZy4KCi0gKipMaWdodHdlaWdodCoqOiBJdCBpcyBhIHRleHQgZm9ybWF0IHRoYXQgaXMgY29uY2lzZSBhbmQgZWFzeSB0byBwYXJzZS4KCi0gKipMYW5ndWFnZS1pbmRlcGVuZGVudCoqOiBXaGlsZSBkZXJpdmVkIGZyb20gSmF2YVNjcmlwdCwgSlNPTiBpcyBsYW5ndWFnZS1hZ25vc3RpYyBhbmQgY2FuIGJlIHVzZWQgd2l0aCBtb3N0IHByb2dyYW1taW5nIGxhbmd1YWdlcywgaW5jbHVkaW5nIFIuCgojIyMjIEpTT04gU3RydWN0dXJlOgoKSlNPTiBkYXRhIGlzIG9yZ2FuaXplZCBpbiB0d28gcHJpbWFyeSBzdHJ1Y3R1cmVzOgoKMS4gKipPYmplY3RzKio6IEFuIHVub3JkZXJlZCBzZXQgb2Yga2V5L3ZhbHVlIHBhaXJzLiBFYWNoIGtleSBpcyBhIHN0cmluZywgYW5kIHRoZSB2YWx1ZSBjYW4gYmUgYSBzdHJpbmcsIG51bWJlciwgb2JqZWN0LCBhcnJheSwgdHJ1ZSwgZmFsc2UsIG9yIG51bGwuIE9iamVjdHMgYXJlIGVuY2xvc2VkIGluIGN1cmx5IGJyYWNlcyBge31gLgogICAtIEV4YW1wbGU6CiAgICAgYGBganNvbgogICAgIHsKICAgICAgICJuYW1lIjogIkFsaWNlIiwKICAgICAgICJhZ2UiOiAzMCwKICAgICAgICJpc1N0dWRlbnQiOiBmYWxzZQogICAgIH0KICAgICBgYGAKCjIuICoqQXJyYXlzKio6IEFuIG9yZGVyZWQgbGlzdCBvZiB2YWx1ZXMuIFZhbHVlcyBjYW4gYmUgb2YgYW55IHR5cGUgKHN0cmluZywgbnVtYmVyLCBvYmplY3QsIGFycmF5LCB0cnVlLCBmYWxzZSwgb3IgbnVsbCkuIEFycmF5cyBhcmUgZW5jbG9zZWQgaW4gc3F1YXJlIGJyYWNrZXRzIGBbXWAuCiAgIC0gRXhhbXBsZToKICAgICBgYGBqc29uCiAgICAgWwogICAgICAgImFwcGxlIiwKICAgICAgICJiYW5hbmEiLAogICAgICAgImNoZXJyeSIKICAgICBdCiAgICAgYGBgCgojIyMjIFVzaW5nIEpTT04gaW4gUgoKSW4gUiwgeW91IGNhbiB1c2UgdGhlIGBqc29ubGl0ZWAgcGFja2FnZSB0byB3b3JrIHdpdGggSlNPTiBkYXRhLiBUaGUgcGFja2FnZSBwcm92aWRlcyBmdW5jdGlvbnMgdG8gcmVhZCBKU09OIGRhdGEgZnJvbSBhIGZpbGUgb3IgVVJMIGFuZCBjb252ZXJ0IGl0IGludG8gUiBkYXRhIGZyYW1lcyBvciBsaXN0cywgYW5kIHZpY2UgdmVyc2EuCgotICoqUmVhZGluZyBKU09OIERhdGEqKjoKCmBgYHtyIGVjaG8gPSBUfQpwcmludChyZXNwb25zZSkKYGBgCgpgYGB7ciBlY2hvID0gVH0KcHJpbnQocmVzcCkKYGBgCgpgYGB7ciBlY2hvID0gVH0KbGlicmFyeShqc29ubGl0ZSkKZGF0YSA8LSBqc29ubGl0ZTo6ZnJvbUpTT04ocmVzcCkKYGBgCgpgZGF0YWAgaXMgbm93IGEgKipsaXN0Kio6CgpgYGB7ciBlY2hvID0gVH0KZGF0YSRzb3VyY2UKYGBgCgpgYGB7ciBlY2hvID0gVH0KZGF0YSRkYXRhCmBgYAoKLSAqKldyaXRpbmcgSlNPTiBEYXRhKio6CgpgYGB7ciBlY2hvID0gVH0KanNvbl9kYXRhIDwtIHRvSlNPTihkYXRhKQpwcmludChqc29uX2RhdGEpCiMgd3JpdGUoanNvbl9kYXRhLCBmaWxlID0gImRhdGEuanNvbiIpCmBgYAoKVW5kZXJzdGFuZGluZyBKU09OIGlzIGVzc2VudGlhbCBmb3IgYWR2YW5jZWQgYW5hbHl0aWNzLCBhcyBpdCBlbmFibGVzIHNlYW1sZXNzIGludGVncmF0aW9uIHdpdGggd2ViIEFQSXMgYW5kIGVmZmljaWVudCBkYXRhIGhhbmRsaW5nIGluIHlvdXIgUiBwcm9qZWN0cy4KCkxldCdzIHBsb3QgdGhlIHRpbWUgc2VyaWVzIG9mIHRoZSBVUyBwb3B1bGF0aW9uIG92ZXIgeWVhcnMgdGhlbjoKCmBgYHtyIGVjaG8gPSBUfQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2dyZXBlbCkKZ2dwbG90KGRhdGEgPSBkYXRhJGRhdGEsIAogICAgICAgYWVzKHggPSBZZWFyLAogICAgICAgICAgIHkgPSBQb3B1bGF0aW9uLCAKICAgICAgICAgICBsYWJlbCA9IFBvcHVsYXRpb24pKSArIAogIGdlb21fcGF0aChzaXplID0gLjI1LCBjb2xvciA9ICJibHVlIiwgZ3JvdXAgPSAxKSArIAogIGdlb21fcG9pbnQoc2l6ZSA9IDIsIGNvbG9yID0gImJsdWUiKSArIAogIGdlb21fbGFiZWxfcmVwZWwoc2l6ZSA9IDMpICsgCiAgZ2d0aXRsZSgiVVMgUG9wdWxhdGlvbiIpICsKICB0aGVtZV9idygpICsgCiAgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAuNSkpCmBgYAoKIyMjIyBNYWtlIGFub3RoZXIgQVBJIGNhbGwgYW5kIGluc3BlY3QgdGhlIGRhdGEKCkZvciBlYWNoIEFQSSB0aGF0IHlvdSB3YW50IHRvIHVzZSB5b3Ugd2lsbCBuZWVkIHRvIHJlYWQgaXRzIGRvY3VtZW50YXRpb24gYW5kIGxlYXJuIGFib3V0IHRoZSBwYXJhbWV0ZXJzIHRoYXQgeW91IG1heSBwYXNzIHRvIGl0LiAKCkkgaGF2ZSBzdHJpcHBlZCB0aGlzIEFQSSBjYWxsIGZyb20gW2h0dHBzOi8vZGF0YXVzYS5pby9wcm9maWxlL3NvYy9lZHVjYXRpb24tbGVnYWwtY29tbXVuaXR5LXNlcnZpY2UtYXJ0cy1tZWRpYS1vY2N1cGF0aW9uc10oaHR0cHM6Ly9kYXRhdXNhLmlvL3Byb2ZpbGUvc29jL2VkdWNhdGlvbi1sZWdhbC1jb21tdW5pdHktc2VydmljZS1hcnRzLW1lZGlhLW9jY3VwYXRpb25zKS4KCllvdSBjYW4gY29weSBhbmQgcGFzdGUgW3RoZSBlbnRpcmUgQVBJIGNhbGxdKGh0dHBzOi8vZGF0YXVzYS5pby9hcGkvZGF0YT9tZWFzdXJlPUF2ZXJhZ2UlMjBXYWdlLEF2ZXJhZ2UlMjBXYWdlJTIwQXBweCBNT0UsUmVjb3JkIENvdW50JmRyaWxsZG93bnM9TWlub3IgT2NjdXBhdGlvbiBHcm91cCZXb3JrZm9yY2UgU3RhdHVzPXRydWUmUmVjb3JkIENvdW50Pj01KSBpbnRvIHlvdXIgYnJvd3NlcnMgbmF2aWdhdGlvbiBiYXIgdG8gb2J0YWluIHRoZSBKU09OIHJlc3BvbnNlIGRpcmVjdGx5LgoKVGhlIGRhdGEgYXJlIG9uIGVkdWNhdGlvbiwgbGVnYWwsIGNvbW11bml0eSBzZXJ2aWNlLCBhcnRzLCAmIG1lZGlhIG9jY3VwYXRpb25zIGluIHRoZSBVU0EuCgpNYWtlIGEgY2FsbCBhbmQgY2hlY2sgdGhlIHNlcnZlciByZXNwb25zZSBzdGF0dXM6CgpgYGB7ciBlY2hvID0gVH0KYXBpX2NhbGwgPC0gcGFzdGUwKGJhc2VFbmRQb2ludCwgCiAgICAgICAgICAgICAgICAgICAiPyIsIAogICAgICAgICAgICAgICAgICAgcGFzdGUoIlBVTVMgT2NjdXBhdGlvbj0yMTAwMDAtMjcwMDAwIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAibWVhc3VyZT1Ub3RhbCBQb3B1bGF0aW9uLFRvdGFsIFBvcHVsYXRpb24gTU9FIEFwcHgsUmVjb3JkIENvdW50IiwKICAgICAgICAgICAgICAgICAgICAgICAgICJkcmlsbGRvd25zPVdhZ2UgQmluIiwKICAgICAgICAgICAgICAgICAgICAgICAgICJXb3JrZm9yY2UgU3RhdHVzPXRydWUiLAogICAgICAgICAgICAgICAgICAgICAgICAgIlJlY29yZCBDb3VudD49NSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIiYiKSkKcmVzcG9uc2UgPC0gR0VUKFVSTGVuY29kZShhcGlfY2FsbCkpCnJlc3BvbnNlJHN0YXR1cwpgYGAKCkNvbnZlcnQgdGhlIHJlc3BvbnNlIHRvIEpTT04gYW5kIHRoYW4gdG8gbGlzdCBhbmQgYSBkYXRhLmZyYW1lOgoKYGBge3IgZWNobyA9IFR9CnJlc3BvbnNlIDwtIHJhd1RvQ2hhcihyZXNwb25zZSRjb250ZW50KQpyZXNwb25zZSA8LSBmcm9tSlNPTihyZXNwb25zZSkKZGF0YSA8LSByZXNwb25zZSRkYXRhCmhlYWQoZGF0YSkKYGBgCgpWaXN1YWxpemUgd2l0aCB7Z2dwbG90Mn06CgpgYGB7ciBlY2hvID0gVCwgZmlnLmhlaWdodD01MH0KZGF0YSRgV2FnZSBCaW5gIDwtIGZhY3RvcihkYXRhJGBXYWdlIEJpbmAsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IHVuaXF1ZShkYXRhJGBXYWdlIEJpbmApKQpnZ3Bsb3QoZGF0YSA9IGRhdGEsIAogICAgICAgYWVzKHggPSBZZWFyLAogICAgICAgICAgIHkgPSBsb2coYFRvdGFsIFBvcHVsYXRpb25gKSwgCiAgICAgICAgICAgY29sb3IgPSBgV2FnZSBCaW5gLAogICAgICAgICAgIGZpbGwgPSBgV2FnZSBCaW5gKSkgKyAKICBnZW9tX3BhdGgoc2l6ZSA9IDEuNSwgZ3JvdXAgPSAxKSArIAogIGdlb21fcG9pbnQoc2l6ZSA9IDEzKSArIAogIGZhY2V0X3dyYXAofmBXYWdlIEJpbmAsIG5jb2wgPSAyKSArCiAgZ2d0aXRsZSgiVVM6IEVkdWNhdGlvbiwgbGVnYWwsIGNvbW11bml0eSBzZXJ2aWNlLCBhcnRzLCAmIG1lZGlhIG9jY3VwYXRpb25zIikgKwogIHRoZW1lX2J3KCkgKyAKICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpICsgCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IC41LCBzaXplID0gNzApKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA0MCkpICsKICB0aGVtZShheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDUwKSkgKyAKICB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gNDApKSArCiAgdGhlbWUoYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSA1MCkpICsgCiAgdGhlbWUobGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDUwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gNTApKSArCiAgdGhlbWUoc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gNDUpKSArCiAgdGhlbWUoc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQpgYGAKCgojIyMgMy4gQ29tcGxpY2F0ZWQgQVBJIHJlc3BvbnNlcyBhbmQgWE1MCgpbV2lraWRhdGFdKGh0dHBzOi8vd3d3Lndpa2lkYXRhLm9yZy93aWtpL1dpa2lkYXRhOk1haW5fUGFnZSkgaXMgYSBmcmVlLCBjb2xsYWJvcmF0aXZlLCBtdWx0aWxpbmd1YWwga25vd2xlZGdlIGJhc2UgdGhhdCBzdG9yZXMgc3RydWN0dXJlZCBkYXRhIHRvIHN1cHBvcnQgV2lraXBlZGlhIGFuZCBvdGhlciBXaWtpbWVkaWEgRm91bmRhdGlvbiBwcm9qZWN0cy4gSXQgc2VydmVzIGFzIGEgY2VudHJhbCByZXBvc2l0b3J5IGZvciBkYXRhLCBlbmFibGluZyBlYXN5IGFjY2VzcyBhbmQgcmV1c2Ugb2YgaW5mb3JtYXRpb24gYWNyb3NzIHZhcmlvdXMgcGxhdGZvcm1zIGFuZCBhcHBsaWNhdGlvbnMuCgpLZXkgRmVhdHVyZXM6CgotIENlbnRyYWxpemVkIERhdGE6IFByb3ZpZGVzIGEgc2luZ2xlIHNvdXJjZSBvZiB0cnV0aCBmb3Igc3RydWN0dXJlZCBkYXRhLCBlbnN1cmluZyBjb25zaXN0ZW5jeSBhY3Jvc3MgbXVsdGlwbGUgV2lraW1lZGlhIHByb2plY3RzLgotIENvbGxhYm9yYXRpdmU6IEFueW9uZSBjYW4gY29udHJpYnV0ZSwgZWRpdCwgYW5kIHVwZGF0ZSBlbnRyaWVzLCBzaW1pbGFyIHRvIFdpa2lwZWRpYS4KLSBNdWx0aWxpbmd1YWw6IFN1cHBvcnRzIGRhdGEgZW50cmllcyBpbiBtdWx0aXBsZSBsYW5ndWFnZXMsIG1ha2luZyBpdCBhY2Nlc3NpYmxlIHRvIGEgZ2xvYmFsIGF1ZGllbmNlLgotIExpbmtlZCBEYXRhOiBJbnRlZ3JhdGVzIHdpdGggb3RoZXIgZGF0YWJhc2VzIGFuZCBrbm93bGVkZ2UgZ3JhcGhzLCBlbmhhbmNpbmcgZGF0YSBjb25uZWN0aXZpdHkgYW5kIHV0aWxpdHkuCgpJbiB0aGUgZm9sbG93aW5nIGV4YW1wbGVzIHdlIHdpbGwgYmUgdXNpbmcgdGhlIFtXaWtpYmFzZSBBUEldKGh0dHBzOi8vd3d3Lm1lZGlhd2lraS5vcmcvd2lraS9XaWtpYmFzZS9BUEkpIHRvIG9idGFpbiBkYXRhIGZyb20gW1dpa2lkYXRhXShodHRwczovL3d3dy53aWtpZGF0YS5vcmcvd2lraS9XaWtpZGF0YTpNYWluX1BhZ2UpLCB0aGUgV29ybGQncyBsYXJnZXN0IG9wZW4ga25vd2xlZGdlIGJhc2UgdGhhdCBjb21wcmlzZXMgYWxsIHN0cnVjdHVyZWQgaW5mb3JtYXRpb24gZnJvbSBXaWtpcGVkaWEgYW5kIG1hbnkgb3RoZXIgc291cmNlcy4gCgpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQpsaWJyYXJ5KFhNTCkgIyAtIHBhcnNlIFhNTCBmb3JtYXQKYGBgCgojIyMjIERhdmlkIEJvd2llIGluIFdpa2lkYXRhCgpXZSB3aWxsIGNvbnRhY3QgdGhlIFdpa2liYXNlIEFQSSB0byBvYnRhaW4gYWxsIGRhdGEgc3RvcmVkIGluIFdpa2lkYXRhIG9uIERhdmlkIEJvd2llICh3aG8gaGFzIGEgKipRIGlkZW50aWZpZXIqKiBvZiBRNTM4MyBpbiB0aGlzIGtub3dsZWRnZSBiYXNlKS4gV2Ugd2lsbCBhc2sgdGhlIFdpa2liYXNlIEFQSSB0byB1c2UgSlNPTiB0byBkZXNjcmliZSBpdHMgcmVzcG9uc2UuIEhlcmUgaXMgaG93IHRoZSBKU09OIHJlc3BvbnNlIHdpbGwgbG9vayBsaWtlOiBbV2lraWJhc2UgQVBJIHJlc3BvbnNlXShodHRwczovL3d3dy53aWtpZGF0YS5vcmcvdy9hcGkucGhwP2FjdGlvbj13YmdldGVudGl0aWVzJmlkcz1RNTM4MyZsYW5ndWFnZXM9ZW4pLgoKYGBge3IgZWNobyA9IFR9CnF1ZXJ5IDwtIAogICdodHRwczovL3d3dy53aWtpZGF0YS5vcmcvdy9hcGkucGhwP2FjdGlvbj13YmdldGVudGl0aWVzJmlkcz1RNTM4MyZsYW5ndWFnZXM9ZW4mZm9ybWF0PWpzb24nCnJlc3BvbnNlIDwtIEdFVChVUkxlbmNvZGUocXVlcnkpKQpyZXNwb25zZSA8LSByYXdUb0NoYXIocmVzcG9uc2UkY29udGVudCkKcmVzcG9uc2UgPC0gZnJvbUpTT04ocmVzcG9uc2UpCmNsYXNzKHJlc3BvbnNlKQpgYGAKCk5vdywgV2lraWRhdGEgaXMgKip2ZXJ5IGNvbXBsZXgqKiAoYW5kIHRodXMgdmVyeSBwb3dlcmZ1bCBhcyBhIGRlc2NyaXB0aXZlIHN5c3RlbTsgYWZ0ZXIgYWxsLCBpdCdzIGdvYWwgaXMgdG8gYmUgYWJsZSB0byBkZXNjcmliZSAqanVzdCBhbnl0aGluZyogdGhhdCB3ZSBjYW4gaW1hZ2luZSwgdGFsaywgYW5kIHdyaXRlIGFib3V0KSwgc28gd2hhdCBgZnJvbUpTT04oKWAgcmV0dXJucyBpcyBhIG5hc3R5LCBuYXN0eSwgbmVzdGVkIGxpc3Q6CgpgYGB7ciBlY2hvID0gVH0KaW5zdGFjZU9mX0RhdmlkQm93aWUgPC0gcmVzcG9uc2UkZW50aXRpZXMkUTUzODMkY2xhaW1zJFAzMQppbnN0YWNlT2ZfRGF2aWRCb3dpZSRtYWluc25hayRkYXRhdmFsdWUkdmFsdWUKYGBgCgpJdCBpcyBuZWNlc3NhcnkgdG8gc3R1ZHkgdGhlIFtXaWtpZGF0YSBEYXRhTW9kZWxdKGh0dHBzOi8vd3d3Lm1lZGlhd2lraS5vcmcvd2lraS9XaWtpYmFzZS9EYXRhTW9kZWwpIGNhcmVmdWxseSBpbiBvcmRlciB0byBiZSBhYmxlIHRvIG5hdmlnYXRlIHRoZSBrbm93bGVkZ2Ugc3RydWN0dXJlcyB0aGF0IGl0IGRlc2NyaWJlczoKCmBgYHtyIGVjaG8gPSBUfQpsYWJlbE9mX0RhdmlkQm93aWUgPC0gcmVzcG9uc2UkZW50aXRpZXMkUTUzODMkbGFiZWxzJGVuJHZhbHVlCmxhYmVsT2ZfRGF2aWRCb3dpZQpgYGAKCkhvd2V2ZXIsIG9uY2UgeW91IGRvIGxlYXJuIGFib3V0IFdpa2lkYXRhJ3MgZGF0YSBtb2RlbC4uLiBUZW5zIG9mIG1pbGxpb25zIG9mIGhpZ2hseSBzdHJ1Y3R1cmVkIGl0ZW1zIGFuZCByZWxhdGlvbnMgYW1vbmcgdGhlbSB3aWxsIGJlY29tZSBhY2Nlc3NpYmxlIHRvIHlvdS4gT3JkZXIgZW1lcmdlcyBmcm9tIGNoYW9zIGluIHRoaXMgY2FzZSwgSSBhc3N1cmUgeW91LiBCZXNpZGVzIEpTT04sIHRoZXJlIGlzIFhNTCAoYW5kIG1hbnkgbW9yZSwgYnV0IHdlIHdpbGwgZm9jdXMgb24gdGhlc2UgdHdvIGZvcm1hdHMpLiAKCiMjIyMgQW4gWE1MIHJlc3BvbnNlIGZyb20gYSBSRVNUIEFQSQoKIyMjIFVuZGVyc3RhbmRpbmcgWE1MCgoqKlhNTCAoZVh0ZW5zaWJsZSBNYXJrdXAgTGFuZ3VhZ2UpKiogaXMgYSB2ZXJzYXRpbGUgdGV4dC1iYXNlZCBmb3JtYXQgdXNlZCBmb3IgcmVwcmVzZW50aW5nIHN0cnVjdHVyZWQgZGF0YS4gSXQgaXMgZGVzaWduZWQgdG8gYmUgYm90aCBodW1hbi1yZWFkYWJsZSBhbmQgbWFjaGluZS1yZWFkYWJsZSwgbWFraW5nIGl0IGEgcG9wdWxhciBjaG9pY2UgZm9yIGRhdGEgaW50ZXJjaGFuZ2UgYmV0d2VlbiBzeXN0ZW1zLgoKIyMjIyBLZXkgQ2hhcmFjdGVyaXN0aWNzIG9mIFhNTDoKCi0gKipTZWxmLWRlc2NyaXB0aXZlKio6IFhNTCB1c2VzIHRhZ3MgdG8gZGVmaW5lIGRhdGEgZWxlbWVudHMsIHByb3ZpZGluZyBjb250ZXh0IGFuZCBzdHJ1Y3R1cmUuCi0gKipIaWVyYXJjaGljYWwqKjogRGF0YSBpcyBvcmdhbml6ZWQgaW4gYSB0cmVlLWxpa2Ugc3RydWN0dXJlLCBhbGxvd2luZyBmb3IgY29tcGxleCBuZXN0ZWQgcmVsYXRpb25zaGlwcy4KLSAqKkZsZXhpYmxlKio6IFlvdSBjYW4gZGVmaW5lIHlvdXIgb3duIHRhZ3MsIG1ha2luZyBYTUwgaGlnaGx5IGFkYXB0YWJsZSB0byBkaWZmZXJlbnQgZGF0YSBuZWVkcy4KLSAqKkNyb3NzLXBsYXRmb3JtKio6IFhNTCBjYW4gYmUgdXNlZCBhbmQgcGFyc2VkIG9uIGFueSBvcGVyYXRpbmcgc3lzdGVtIG9yIHByb2dyYW1taW5nIGxhbmd1YWdlLgoKIyMjIyBYTUwgU3RydWN0dXJlOgoKWE1MIGRvY3VtZW50cyBjb25zaXN0IG9mIGVsZW1lbnRzIGVuY2xvc2VkIGluIHRhZ3MsIHdpdGggYSByb290IGVsZW1lbnQgdGhhdCBjb250YWlucyBhbGwgb3RoZXIgZWxlbWVudHMuCgotICoqRXhhbXBsZSBvZiBhIFNpbXBsZSBYTUwgRG9jdW1lbnQqKjoKICBgYGB4bWwKICA8cGVyc29uPgogICAgPG5hbWU+Sm9obiBEb2U8L25hbWU+CiAgICA8YWdlPjMwPC9hZ2U+CiAgICA8ZW1haWw+am9obi5kb2VAZXhhbXBsZS5jb208L2VtYWlsPgogIDwvcGVyc29uPgogIGBgYAoKLSAqKkV4YW1wbGUgd2l0aCBOZXN0ZWQgRWxlbWVudHMqKjoKICBgYGB4bWwKICA8Ym9va3N0b3JlPgogICAgPGJvb2s+CiAgICAgIDx0aXRsZT5FZmZlY3RpdmUgUiBQcm9ncmFtbWluZzwvdGl0bGU+CiAgICAgIDxhdXRob3I+SmFuZSBTbWl0aDwvYXV0aG9yPgogICAgICA8cHJpY2U+MjkuOTk8L3ByaWNlPgogICAgPC9ib29rPgogICAgPGJvb2s+CiAgICAgIDx0aXRsZT5EYXRhIFNjaWVuY2Ugd2l0aCBSPC90aXRsZT4KICAgICAgPGF1dGhvcj5Kb2huIERvZTwvYXV0aG9yPgogICAgICA8cHJpY2U+MzkuOTk8L3ByaWNlPgogICAgPC9ib29rPgogIDwvYm9va3N0b3JlPgogIGBgYAoKTGV0J3MgZ2V0IGJhY2sgdG8gdGhlIFdpa2liYXNlIEFQSSBhbmQgYXNrIGZvciB0aGUgc2FtZSBkYXRhIG9uIERhdmlkIEJvd2llIHdyYXBwZWQgaW4gYW4gW1hNTCByZXNwb25zZV0oaHR0cHM6Ly93d3cud2lraWRhdGEub3JnL3cvYXBpLnBocD9hY3Rpb249d2JnZXRlbnRpdGllcyZpZHM9UTUzODMmbGFuZ3VhZ2VzPWVuJmZvcm1hdD14bWwpOgoKYGBge3IgZWNobyA9IFR9CnF1ZXJ5IDwtIAogICdodHRwczovL3d3dy53aWtpZGF0YS5vcmcvdy9hcGkucGhwP2FjdGlvbj13YmdldGVudGl0aWVzJmlkcz1RNTM4MyZsYW5ndWFnZXM9ZW4mZm9ybWF0PXhtbCcKcmVzcG9uc2UgPC0gR0VUKFVSTGVuY29kZShxdWVyeSkpCnJlc3BvbnNlIDwtIHJhd1RvQ2hhcihyZXNwb25zZSRjb250ZW50KQpyZXNwb25zZSA8LSB4bWxQYXJzZShyZXNwb25zZSkKcmVzcG9uc2UgPC0geG1sVG9MaXN0KHJlc3BvbnNlKQpgYGAKCioqTm90ZToqKiBgZm9ybWF0PXhtbGAuCgpUaGUgRW5nbGlzaCBsYWJlbCBmb3IgRGF2aWQgQm93aWUgaW4gV2lraWRhdGE6CgpgYGB7ciBlY2hvID0gVH0KcmVzcG9uc2UkZW50aXRpZXMkZW50aXR5JGxhYmVscyRsYWJlbApgYGAKCmBgYHtyIGVjaG8gPSBUfQpjbGFzcyhyZXNwb25zZSRlbnRpdGllcyRlbnRpdHkkbGFiZWxzJGxhYmVsKQpgYGAKCiMjIyMgNC40IEFsbCBuYW1lcyBvZiBEYXZpZCBCb3dpZQoKTm93LCBEYXZpZCBCb3dpZSwgaW4gYWxsIGxhbmd1YWdlcyBhdmFpbGFibGUgaW4gV2lraWRhdGEuIEZpcnN0LCB3ZSBnZXQgdGhlIGRhdGEuCgpgYGB7ciBlY2hvID0gVH0KcXVlcnkgPC0gCiAgJ2h0dHBzOi8vd3d3Lndpa2lkYXRhLm9yZy93L2FwaS5waHA/YWN0aW9uPXdiZ2V0ZW50aXRpZXMmaWRzPVE1MzgzJmZvcm1hdD1qc29uJwpyZXNwb25zZSA8LSBHRVQoVVJMZW5jb2RlKHF1ZXJ5KSkKcmVzcG9uc2UgPC0gcmF3VG9DaGFyKHJlc3BvbnNlJGNvbnRlbnQpCnJlc3BvbnNlIDwtIGZyb21KU09OKHJlc3BvbnNlKQpgYGAKClNlY29uZDogc3R1ZHkgdGhlIHN0cnVjdHVyZSBvZiB0aGUgcmVzcG9uc2UsIGFuZCB0aGVuIGBsYXBwbHkoKWAgYWNyb3NzIHRoZSBhcHByb3ByaWF0ZSBzZXQgb2YgbGlzdHM6IAoKYGBge3IgZWNobyA9IFR9CmxhYmVscyA8LSBsYXBwbHkocmVzcG9uc2UkZW50aXRpZXMkUTUzODMkbGFiZWxzLCBmdW5jdGlvbih4KSB7CiAgcGFzdGUwKHgkdmFsdWUsICIgKCIsIHgkbGFuZ3VhZ2UsICIpIikKfSkKbGFiZWxzIDwtIHBhc3RlKGxhYmVscywgY29sbGFwc2UgPSAiLCAiKQpwcmludChsYWJlbHMpCmBgYAoKRGlkbid0IEkgdGVsbCB5b3UgaG93IGxpc3RzIGFuZCBmdW5jdGlvbmFsIHByb2dyYW1taW5nIGFyZSBpbXBvcnRhbnQgaW4gUj8KCiMjIyA0LiBQYXJzZSBQREYgZmlsZXMgZnJvbSBSCgpTb29uZXIgb3IgbGF0ZXIgeW91IHdpbGwgd2FudCB0byBleHRyYWN0IHRhYnVsYXIgZGF0YSBmcm9tIFBERiBmaWxlcy4uLgoKYGBge3IgZWNobyA9IFR9CiMgTG9hZCB0aGUgdGFidWxpemVyIGxpYnJhcnkKIyBEb3dubG9hZCBEb3dubG9hZCB0aGUgTWljcm9zb2Z0IEJ1aWxkIG9mIE9wZW5KREsKIyBodHRwczovL2xlYXJuLm1pY3Jvc29mdC5jb20vZW4tdXMvamF2YS9vcGVuamRrL2Rvd25sb2FkCiMgRmluZCB5b3VyIEpBVkFfSE9NRSAodG8gYmUgZXhlbXBsaWZpZWQgaW4gdGhlIHNlc3Npb24pIGFuZCB0aGVuOgojIFN5cy5zZXRlbnYoSkFWQV9IT01FPSJDOi9Qcm9ncmFtIEZpbGVzL01pY3Jvc29mdC9qZGstMTEuMC4yMy45LWhvdHNwb3QiKQpsaWJyYXJ5KHRhYnVsYXBkZikKCiMgRGVmaW5lIHRoZSBwYXRoIHRvIHRoZSBQREYgZmlsZQpkYXRhX2RpciA8LSBwYXN0ZTAoZ2V0d2QoKSwgIi9fZGF0YS8iKQpwZGZfcGF0aCA8LSBwYXN0ZTAoZGF0YV9kaXIsICJtdGNhcnMucGRmIikKCiMgRXh0cmFjdCBhbGwgdGFibGVzIGZyb20gdGhlIFBERgp0YWJsZXMgPC0gdGFidWxhcGRmOjpleHRyYWN0X3RhYmxlcyhwZGZfcGF0aCkKYGBgCgpSZXR1cm5lZCBhIGxpc3QgYHRhYmxlc2A6CgpgYGB7ciBlY2hvID0gVH0KdGFibGVzCmBgYAoKRXh0cmFjdCB0byBgZGF0YS5mcmFtZWAsIG9uZSBieSBvbmU6CgpgYGB7ciBlY2hvID0gVH0KbXRjYXJzIDwtIHRhYmxlc1tbMV1dCmlyaXMgPC0gdGFibGVzW1syXV0KYmlvX2RhdGEgPC0gdGFibGVzW1szXV0KYGBgCgojIyMgNS4gQ2FzZSBTdHVkeTogV29ybGQgQmFuayBEYXRhCgotIExlYXJuIGFib3V0IHRoZSBXREkgcGFja2FnZSAtIFdvcmxkIEJhbmsgZGF0YSBpbiBSIC0gaW5zdGFsbCBpdCwgYW5kIGxlYXJuIGhvdyB0byB1c2UgaXQ6IFtXREldKGh0dHBzOi8vZ2l0aHViLmNvbS92aW5jZW50YXJlbGJ1bmRvY2svV0RJKQotIEhlcmUncyBzb21lIGNvZGUgdG8gZ2V0IHlvdSBzdGFydGVkOiAKCmBgYHtyIGVjaG8gPSBUfQojIGluc3RhbGwucGFja2FnZXMoIldESSkKbGlicmFyeShXREkpCldESXNlYXJjaCgnZ2RwJykKYGBgCgpgYGB7ciBlY2hvID0gVH0KZGF0YV9zZXQgPSBXREkoaW5kaWNhdG9yPSdOWS5HRFAuTUtUUC5DRCcsIAogICAgICAgICAgICAgICBjb3VudHJ5PWMoJ1VTJyksIAogICAgICAgICAgICAgICBzdGFydD0xOTYwLCBlbmQ9MjAyMikKcHJpbnQoZGF0YV9zZXQpCmBgYAoKWW91IHdpbGwgbmVlZCB0byBsZWFybiBhYm91dCBbVGhlIFdvcmxkIEJhbmsgRGF0YV0oaHR0cHM6Ly9kYXRhLndvcmxkYmFuay5vcmcvaW5kaWNhdG9yKSBpbiBvcmRlciB0byB1bmRlcnN0YW5kIHRoZSBpbmRpY2F0b3JzISAKCmBgYHtyIGVjaG8gPSBUfQpkYXRhX3NldCA9IFdESShpbmRpY2F0b3I9J05ZLkdEUC5NS1RQLkNEJywgCiAgICAgICAgICAgICAgIGNvdW50cnk9YygnUlMnLCAnSFInLCAnU0knLCAnTUUnLCAnQkEnLCAnTUsnKSwgCiAgICAgICAgICAgICAgIHN0YXJ0PTE5OTAsIAogICAgICAgICAgICAgICBlbmQ9MjAyMykKcHJpbnQoZGF0YV9zZXQpCmBgYAoKYGBge3IgZWNobyA9IFR9CmdncGxvdChkYXRhX3NldCwgCiAgICAgICBhZXMoeCA9IHllYXIsIAogICAgICAgICAgIHkgPSBOWS5HRFAuTUtUUC5DRCwgCiAgICAgICAgICAgY29sb3IgPSBjb3VudHJ5KSkgKyAKICBnZW9tX2xpbmUoKSArCiAgeGxhYignWWVhcicpICsgeWxhYignR0RQIHBlciBjYXBpdGEnKSArIAogIHRoZW1lX2J3KCkgKyAKICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKKipZb3VyIHRhc2sgaXMgdG8gcHJvdmlkZSBhbiBFREEgYmFzZWQgb24gV29ybGQgQmFuayBEYXRhIG9uIHRoZSBob3cgZXgtWXUgY291bnRyaWVzIGRldmVsb3BlZCBhZnRlciAxOTkwLioqIFVzZSBnZ3Bsb3QyIGFuZCBQbG90bHkgZm9yIHZpc3VhbGl6YXRpb25zLiBTdHVkeSBhbnkgV29ybGQgQmFuayBEYXRhIGluZGljYXRvcnMgdGhhdCB5b3UgbWlnaHQgZmluZCBpbnRlcmVzdGluZyBmb3IgdGhlIGNvbXBhcmF0aXZlIHN0dWR5IGF0IGhhbmQsIG9idGFpbmVkIHRoZW0gdmlhIHRoZSBgV0RJYCBwYWNrYWdlLCBjcm9zcy10YWJ1bGF0ZSBhZ2FpbnN0IGVhY2ggb3RoZXIgaWYgbmVjZXNzYXJ5IG9yIGludGVyZXN0aW5nLCBhbmQgcHJvdmlkZSB5b3VyIGluc2lnaHRzIGluIFIgTWFya2Rvd24uCgoKIyMjIDYuIFIgRGF0YSBFY29zeXN0ZW0KCkZpcnN0LCBoYXZlIGEgbG9vayBhdCB0aGlzIHRyZWFzdXJlOiBbQSBsaXN0IG9mIG92ZXIgMSwwMDAgZGF0YXNldHMgYXZhaWxhYmxlIGluIFIgcGFja2FnZXNdKGh0dHBzOi8vdmluY2VudGFyZWxidW5kb2NrLmdpdGh1Yi5pby9SZGF0YXNldHMvZGF0YXNldHMuaHRtbCkuLi4KCk5vdywgaGVyZSBpcyBhIGxpc3Qgb2Ygc29tZSBSIHBhY2thZ2VzIHNwZWNpZmljYWxseSBkZXNpZ25lZCB0byBjb25uZWN0IHRvIHRoaXJkLXBhcnR5IG9wZW4tZGF0YSByZXNvdXJjZXMgYW5kIHByb3ZpZGUgYGRhdGEuZnJhbWVzYDoKCjEuICoqV0RJKio6IEFjY2Vzc2VzIFdvcmxkIEJhbmsgZGF0YS4KICAgLSBHaXRIdWI6IFtXREldKGh0dHBzOi8vZ2l0aHViLmNvbS92aW5jZW50YXJlbGJ1bmRvY2svV0RJKQogICAKMi4gKip0aWR5Y2Vuc3VzKio6IEFjY2Vzc2VzIFVTIENlbnN1cyBCdXJlYXUgZGF0YSwgaW5jbHVkaW5nIHRoZSBBbWVyaWNhbiBDb21tdW5pdHkgU3VydmV5LgogICAtIEdpdEh1YjogW3RpZHljZW5zdXNdKGh0dHBzOi8vZ2l0aHViLmNvbS93YWxrZXJrZS90aWR5Y2Vuc3VzKQogICAKMy4gKipybmF0dXJhbGVhcnRoKio6IERvd25sb2FkcyBjb3VudHJ5IGJvdW5kYXJpZXMgYW5kIG90aGVyIG5hdHVyYWwgZWFydGggbWFwIGRhdGEuCiAgIC0gR2l0SHViOiBbcm5hdHVyYWxlYXJ0aF0oaHR0cHM6Ly9naXRodWIuY29tL3JvcGVuc2NpL3JuYXR1cmFsZWFydGgpCiAgIAo0LiAqKkV1cm9zdGF0Kio6IEFjY2Vzc2VzIGRhdGEgZnJvbSB0aGUgRXVyb3N0YXQgZGF0YWJhc2UuCiAgIC0gR2l0SHViOiBbZXVyb3N0YXRdKGh0dHBzOi8vZ2l0aHViLmNvbS9yT3Blbkdvdi9ldXJvc3RhdCkKICAgCjUuICoqcXVhbmRsKio6IEFjY2Vzc2VzIGZpbmFuY2lhbCwgZWNvbm9taWMsIGFuZCBhbHRlcm5hdGl2ZSBkYXRhc2V0cyBmcm9tIFF1YW5kbC4KICAgLSBHaXRIdWI6IFtxdWFuZGxdKGh0dHBzOi8vZ2l0aHViLmNvbS9xdWFuZGwvcXVhbmRsLXIpCiAgIAo2LiAqKmZhb2FwaSoqOiBBY2Nlc3NlcyBkYXRhIGZyb20gdGhlIEZBTyAoRm9vZCBhbmQgQWdyaWN1bHR1cmUgT3JnYW5pemF0aW9uKSBkYXRhYmFzZS4KICAgLSBHaXRIdWI6IFtmYW9hcGldKGh0dHBzOi8vZ2l0aHViLmNvbS9TV1MtTWV0aG9kb2xvZ3kvZmFvc3dzVHJhZGUpCiAgIAo4LiAqKnJub2FhKio6IEFjY2Vzc2VzIGRhdGEgZnJvbSB0aGUgTmF0aW9uYWwgT2NlYW5pYyBhbmQgQXRtb3NwaGVyaWMgQWRtaW5pc3RyYXRpb24gKE5PQUEpLgogICAtIEdpdEh1YjogW3Jub2FhXShodHRwczovL2dpdGh1Yi5jb20vcm9wZW5zY2kvcm5vYWEpCiAgIAo5LiAqKnR1YmVyKio6IEFjY2Vzc2VzIFlvdVR1YmUgQVBJIGZvciB2aWRlbyBhbmQgY2hhbm5lbCBkYXRhLgogICAtIEdpdEh1YjogW3R1YmVyXShodHRwczovL2dpdGh1Yi5jb20vZ29qaXBsdXMvdHViZXIpCiAgIAoxMC4gKippbWZyKio6IEFjY2Vzc2VzIGRhdGEgZnJvbSB0aGUgSW50ZXJuYXRpb25hbCBNb25ldGFyeSBGdW5kIChJTUYpIGRhdGFiYXNlLgogICAgLSBHaXRIdWI6IFtpbWZyXShodHRwczovL2dpdGh1Yi5jb20vY2hyaXN0b3BoZXJnYW5kcnVkL2ltZnIpCiAgICAKMTEuICoqcmRocyoqOiBBY2Nlc3NlcyBEZW1vZ3JhcGhpYyBhbmQgSGVhbHRoIFN1cnZleXMgKERIUykgUHJvZ3JhbSBkYXRhLgogICAgLSBHaXRIdWI6IFtyZGhzXShodHRwczovL2dpdGh1Yi5jb20vcm9wZW5zY2kvcmRocykKICAgIAoxMi4gKipibHNjcmFwZVIqKjogQWNjZXNzZXMgZGF0YSBmcm9tIHRoZSBCdXJlYXUgb2YgTGFib3IgU3RhdGlzdGljcyAoQkxTKS4KICAgIC0gR2l0SHViOiBbYmxzY3JhcGVSXShodHRwczovL2dpdGh1Yi5jb20va2ViZXJ3ZWluL2Jsc2NyYXBlUikKICAgIAoxMy4gKipyV0JjbGltYXRlKio6IEFjY2Vzc2VzIFdvcmxkIEJhbmsgQ2xpbWF0ZSBEYXRhIEFQSS4KICAgIC0gR2l0SHViOiBbcldCY2xpbWF0ZV0oaHR0cHM6Ly9naXRodWIuY29tL3JvcGVuc2NpL3JXQmNsaW1hdGUpCgojIyMgRnVydGhlciBSZWFkaW5ncwoKLSBbQW4gSW50cm9kdWN0aW9uIHRvIEpTT04sIGZyb20gRGlnaXRhbCBPY2Vhbl0oaHR0cHM6Ly93d3cuZGlnaXRhbG9jZWFuLmNvbS9jb21tdW5pdHkvdHV0b3JpYWxzL2FuLWludHJvZHVjdGlvbi10by1qc29uKQotIFtJbnRyb2R1Y3Rpb24gdG8gWE1MLCBmcm9tIElCTSwgYnkgRG91ZyBUaWR3ZWxsXShodHRwczovL3d3dy5pYm0uY29tL2RldmVsb3BlcndvcmtzL3htbC90dXRvcmlhbHMveG1saW50cm8veG1saW50cm8uaHRtbCkKLSBbSG93IHRvIEFjY2VzcyBBbnkgUkVTVGZ1bCBBUEkgVXNpbmcgdGhlIFIgTGFuZ3VhZ2UsIGJ5IEFuZHJldyBDYXJwZW50ZXJdKGh0dHBzOi8vd3d3LnByb2dyYW1tYWJsZXdlYi5jb20vbmV3cy9ob3ctdG8tYWNjZXNzLWFueS1yZXN0ZnVsLWFwaS11c2luZy1yLWxhbmd1YWdlL2hvdy10by8yMDE3LzA3LzIxKQoKIyMjIEltcG9ydGFudCBzb3VyY2VzLCBkb2N1bWVudGF0aW9uLCBldGMuCgotIFtYTUwgVHV0b3JpYWwsIFczQ10oaHR0cHM6Ly93d3cudzNzY2hvb2xzLmNvbS94bWwvKQotIFtKU09OIGRlZmluZWRdKGh0dHBzOi8vd3d3Lmpzb24ub3JnL2pzb24tZW4uaHRtbCkKLSBbSlMgSlNPTiBmcm9tIHczc2Nob29sc10oaHR0cHM6Ly93d3cudzNzY2hvb2xzLmNvbS9qcy9qc19qc29uX2ludHJvLmFzcCkKCgojIyMgUiBNYXJrZG93bgoKW1IgTWFya2Rvd25dKGh0dHBzOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tLykgaXMgd2hhdCBJIGhhdmUgdXNlZCB0byBwcm9kdWNlIHRoaXMgYmVhdXRpZnVsIE5vdGVib29rLiBXZSB3aWxsIGxlYXJuIG1vcmUgYWJvdXQgaXQgbmVhciB0aGUgZW5kIG9mIHRoZSBjb3Vyc2UsIGJ1dCBpZiB5b3UgYWxyZWFkeSBmZWVsIHJlYWR5IHRvIGRpdmUgZGVlcCwgaGVyZSdzIGEgYm9vazogW1IgTWFya2Rvd246IFRoZSBEZWZpbml0aXZlIEd1aWRlLCBZaWh1aSBYaWUsIEouIEouIEFsbGFpcmUsIEdhcnJldHQgR3JvbGVtdW5kcy5dKGh0dHBzOi8vYm9va2Rvd24ub3JnL3lpaHVpL3JtYXJrZG93bi8pIAoKCgoqKioKR29yYW4gUy4gTWlsb3Zhbm92acSHCgpEYXRhS29sZWt0aXYsIDIwMjQuCgpjb250YWN0OiBnb3Jhbi5taWxvdmFub3ZpY0BkYXRha29sZWt0aXYuY29tCgohW10oX2ltZy9ES19Mb2dvXzEwMC5wbmcpCgoqKioKTGljZW5zZTogW0dQTHYzXShodHRwOi8vd3d3LmdudS5vcmcvbGljZW5zZXMvZ3BsLTMuMC50eHQpClRoaXMgTm90ZWJvb2sgaXMgZnJlZSBzb2Z0d2FyZTogeW91IGNhbiByZWRpc3RyaWJ1dGUgaXQgYW5kL29yIG1vZGlmeSBpdCB1bmRlciB0aGUgdGVybXMgb2YgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGFzIHB1Ymxpc2hlZCBieSB0aGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uLCBlaXRoZXIgdmVyc2lvbiAzIG9mIHRoZSBMaWNlbnNlLCBvciAoYXQgeW91ciBvcHRpb24pIGFueSBsYXRlciB2ZXJzaW9uLgpUaGlzIE5vdGVib29rIGlzIGRpc3RyaWJ1dGVkIGluIHRoZSBob3BlIHRoYXQgaXQgd2lsbCBiZSB1c2VmdWwsIGJ1dCBXSVRIT1VUIEFOWSBXQVJSQU5UWTsgd2l0aG91dCBldmVuIHRoZSBpbXBsaWVkIHdhcnJhbnR5IG9mIE1FUkNIQU5UQUJJTElUWSBvciBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRS4gIFNlZSB0aGUgR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UgZm9yIG1vcmUgZGV0YWlscy4KWW91IHNob3VsZCBoYXZlIHJlY2VpdmVkIGEgY29weSBvZiB0aGUgR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UgYWxvbmcgd2l0aCB0aGlzIE5vdGVib29rLiBJZiBub3QsIHNlZSA8aHR0cDovL3d3dy5nbnUub3JnL2xpY2Vuc2VzLz4uCgoqKioKCg==